package org.ant.handler;
import java.util.ArrayList;
import java.util.Hashtable;

import org.ant.parser.*;
import org.ant.handler.typeChecking.*;

public class TACGenerator {
	
	private static TACGenerator tac = null;
	
	public static TACGenerator getInstance(){
		if(tac == null){
			tac = new TACGenerator();
		}
		return tac;
	}
	
	private StringBuffer output ;
	private SymbolTableHandler symbolTable;
	private registerAllocater registerAlloc;
	private ClassDesc currentClass;
	private MethodDesc currentMethod;
	private Hashtable <String,String> tempsTypeList;
	
	public String getOutput(){
		return output.toString();
	}
	
	public void generateTAC(Node root, SymbolTableHandler SymoblTable){
		if ( null == root || null == SymoblTable )
			return;
		ArrayList <String> childTemps = new ArrayList <String>();
		output = new StringBuffer();
		symbolTable = SymoblTable;
		registerAlloc = new registerAllocater();
		tempsTypeList = new Hashtable <String,String>();
		output.append("//Generated by QUEEN ANT xyz Compiler\n");
		output.append("//Start of 3AC\n");
		declInitialVaribles();
		findContext(root);
		AST2TAC(root, childTemps);
		output.append("//End of 3AC\n");
		
		printTACCodes();
	}
	
	private void findContext(Node root){
		Node currentNode = root;
		while (currentNode != null){
			if (currentNode instanceof ASTClassDecl){
				currentClass = symbolTable.getClassByName(
					((ASTClassDecl)currentNode).getName());	
				break;
			}
			currentNode = currentNode.jjtGetParent();
		}
		if (currentNode != null){
			currentNode = root;
			while (currentNode != null){
				if (currentNode instanceof ASTMethodDecl){
					currentMethod = currentClass.getMethodByName(
							((ASTMethodDecl)currentNode).getName());
					break;
				}
				currentNode = currentNode.jjtGetParent();
			}
		}
	}
	
	private void AST2TAC(Node node, ArrayList <String> parentTemps){
		ArrayList <String> childTemps = new ArrayList <String>();
		boolean customizedProcess = 
			preProcess(node, parentTemps, childTemps);
		if (!customizedProcess){
			for (int i = 0; i < node.jjtGetNumChildren(); i++) {
				AST2TAC(node.jjtGetChild(i),childTemps);
			}
		}
		postProcess(node,childTemps);
	}
	
	private boolean preProcess(Node node, 
			ArrayList <String> parentTemps,ArrayList <String> childTemps){
		if(node instanceof ASTProgram){
			ASTProgram  nodeProg = (ASTProgram) node;
			output.append("//Start of " + nodeProg.toString() +"\n");
		}
		else if(node instanceof ASTMainClass){
			ASTMainClass  nodeMain = (ASTMainClass) node;
			output.append("//Start of " + nodeMain.toString() +"\n");
		}
		else if(node instanceof ASTMainClassBody){
			registerAlloc.entryBlock();
		}
		else if(node instanceof ASTClassDecl){
			ASTClassDecl  nodeClassDecl= (ASTClassDecl) node;
			currentClass = symbolTable.getClassByName(nodeClassDecl.getName());
			registerAlloc.entryBlock();
			output.append("//Start of " + nodeClassDecl.toString() +"\n");
		}
		else if(node instanceof ASTMethodDecl){
			ASTMethodDecl  nodeMethodDecl= (ASTMethodDecl) node;
			registerAlloc.entryBlock();
			currentMethod = currentClass.getMethodByName(nodeMethodDecl.getName());
			output.append(currentClass.getClassName()+"." 
					+ nodeMethodDecl.getName() +":\n");
		}
		else if(node instanceof ASTVarDecl){
			ASTVarDecl  nodeVarDecl= (ASTVarDecl) node;
			/*if (null == currentMethod)
				output.append("//No code for Member field "
						+ nodeVarDecl.getName() +"\n");
			else{
				output.append(nodeVarDecl.getName() +" := _stack_ptr \n");
				output.append(registerAlloc.getStackSpace());
				
			}*/
		}
		else if(node instanceof ASTStatement){
			ASTStatement nodeStatement = (ASTStatement) node;
			return preStatementProcess(nodeStatement,parentTemps,childTemps);
			
		}
		else if(node instanceof ASTExp){
			ASTExp nodeExp= (ASTExp) node;
			preExpProcess(nodeExp,parentTemps,childTemps);
		}
		else if(node instanceof ASTExpList){
			ASTExpList nodeExpList = (ASTExpList) node;
			parentTemps.add(Integer.toString(
					nodeExpList.jjtGetNumChildren() + 1));
		}
		else if(node instanceof ASTNewInstance){
			ASTNewInstance newInstanceNode = (ASTNewInstance) node;
			String regTemp = registerAlloc.get();
			parentTemps.add(regTemp);
			childTemps.add(regTemp);
			
			tempsTypeList.put(regTemp, 
					((SimpleNode)(newInstanceNode.jjtGetChild(0))).getName());
		}
		else if(node instanceof ASTnewIntArray){
			String regTemp = registerAlloc.get();
			parentTemps.add(regTemp);
			childTemps.add(regTemp);
			
		}
		else if(node instanceof ASTnewDoubleArray){
			String regTemp = registerAlloc.get();
			parentTemps.add(regTemp);
			childTemps.add(regTemp);
			
		}
		else if(node instanceof ASTTerminal){
			ASTTerminal nodeTerminal= (ASTTerminal) node;
			if (isMemberField(nodeTerminal.getName())){
				String resReg = registerAlloc.get();
				childTemps.add(resReg);
				parentTemps.add(resReg);
			}else
				parentTemps.add(nodeTerminal.getName());
		}
		else if(node instanceof ASTOp){
			ASTOp nodeOp = (ASTOp) node;
			parentTemps.add(nodeOp.getName());
		}
		else if (node instanceof ASTId){
			ASTId nodeId= (ASTId) node;
			if (isMemberField(nodeId.getName())){
				String resReg = registerAlloc.get();
				childTemps.add(resReg);
				parentTemps.add(resReg);
			}
			else
				parentTemps.add( nodeId.getName());
		}
		return false;
	}

	private void postProcess(Node node, ArrayList <String> childTemps){
		if(node instanceof ASTProgram){
			ASTProgram  nodeProg = (ASTProgram) node;
			output.append("//End of " + nodeProg.toString() +"\n");
		}
		else if(node instanceof ASTMainClass){
			ASTMainClass  nodeMain = (ASTMainClass) node;
			output.append("//End of " + nodeMain.toString() +"\n");
		}
		else if(node instanceof ASTMainClassBody){
			registerAlloc.leaveBlock();
		}
		else if(node instanceof ASTClassDecl){
			ASTClassDecl  nodeClassDecl= (ASTClassDecl) node;
			registerAlloc.leaveBlock();
			output.append("//End of " + nodeClassDecl.toString() +"\n");
		}
		else if(node instanceof ASTMethodDecl){
			ASTMethodDecl  nodeMethodDecl= (ASTMethodDecl) node;
			currentMethod = null;
			int stackUsage = registerAlloc.leaveBlock();
			/*if (stackUsage > 0)
				output.append("_stack_ptr := _stack_ptr - " 
					+ stackUsage + ":\n");*/
		}
		else if(node instanceof ASTStatement){
			ASTStatement nodeStatement= (ASTStatement) node;
			postStatementProcess(nodeStatement,childTemps);
		}
		else if(node instanceof ASTExp){
			ASTExp nodeExp = (ASTExp) node;
			postExpProcess(nodeExp,childTemps);
		}
		else if(node instanceof ASTExpList){
			ASTExpList nodeExpList = (ASTExpList) node;
			for(int i = 0; i < childTemps.size(); i++)
				output.append("param "+ childTemps.get(i) + "\n");
		}
		else if(node instanceof ASTNewInstance){
			output.append(childTemps.get(0) + " := _heap_ptr\n");
			int fields = getClassFields(tempsTypeList.get(childTemps.get(0)));
			String reg = registerAlloc.get();
			output.append(reg + " := " + fields + " * _w\n");
			output.append("_heap_ptr := _heap_ptr + " + reg + "\n");
		}
		else if(node instanceof ASTnewIntArray){
			output.append(childTemps.get(0) + " := _heap_ptr + " +
					"_w\t//the first word reserved for length\n");
			output.append("*_heap_ptr := " + childTemps.get(1)+"\n");
			String reg = registerAlloc.get();
			output.append(reg + " := " + childTemps.get(1) +" + 1\n");
			String regW = registerAlloc.get();
			output.append(regW + " := " + reg +" * _w\n");
			output.append("_heap_ptr := _heap_ptr + " + regW + "\n");
		}
		else if(node instanceof ASTnewDoubleArray){
			output.append(childTemps.get(0) + " := _heap_ptr + " +
					"_w\t//the first word reserved for length\n");
			output.append("*_heap_ptr := " + childTemps.get(1)+"\n");
			String reg = registerAlloc.get();
			output.append(reg + " := " + childTemps.get(1) +" + 1\n");
			String regW = registerAlloc.get();
			output.append(regW + " := " + reg +" * _w\n");
			output.append("_heap_ptr := _heap_ptr + " + regW + "\n");
		}
		else if(node instanceof ASTreturnExp){
			ASTreturnExp nodeReturnExp = (ASTreturnExp) node;
			output.append("return " + childTemps.get(0)+ "\n");
		}
		else if(node instanceof ASTTerminal){
			ASTTerminal nodeTerminal= (ASTTerminal) node;
			if (isMemberField(nodeTerminal.getName())){
				output.append(childTemps.get(0)+ " := _thiscall["
						+ nodeTerminal.getName() +"]\n");
				if (tempsTypeList.get(childTemps.get(0)) != null)
					tempsTypeList.remove(childTemps.get(0));
				tempsTypeList.put(childTemps.get(0), currentClass.getClassName());
			}
		}
		else if (node instanceof ASTId){
			ASTId nodeId= (ASTId) node;
			if (isMemberField(nodeId.getName())){
				output.append(childTemps.get(0)+ " := & _thiscall["
						+ nodeId.getName() +"]\n");
				if (tempsTypeList.get(childTemps.get(0)) != null)
					tempsTypeList.remove(childTemps.get(0));
				tempsTypeList.put(childTemps.get(0), "*");
			}
		}
	}
	
	private boolean preStatementProcess(ASTStatement node,
			ArrayList <String> parentTemps,
			ArrayList <String> childTemps){
		if (node.getName().equals("System.out.println ( Exp );")){
			parentTemps.add(registerAlloc.get());
		}
		else if (node.getName().equals("block")){
			registerAlloc.entryBlock();
		}
		else if (node.getName().equals("id = Exp ;")){
		}
		else if (node.getName().equals("id [ Exp ]= Exp ;")){
		}
		else if (node.getName().equals("if ( Exp ) Statement else Statement")){
			ArrayList <String> rootTemps = new ArrayList <String>();
			AST2TAC(node.jjtGetChild(0), rootTemps);
			String labelPrefix = currentClass.getClassName() + "."
				+ currentMethod.getMethodName()+"." + registerAlloc.getLabel()+".";
			//registerAlloc.backRoll();
			output.append("if "+rootTemps.get(0)+" <> true goto " 
					+ labelPrefix + "ELSE\n");
			AST2TAC(node.jjtGetChild(1), rootTemps);
			output.append("goto " + labelPrefix + "JOINT\n");
			output.append(labelPrefix + "ELSE:\n");
			AST2TAC(node.jjtGetChild(2), rootTemps);
			output.append(labelPrefix + "JOINT:\n");
			return true;
		}
		else if (node.getName().equals("while ( Exp ) Statement")){
			ArrayList <String> rootTemps = new ArrayList <String>();
			String labelPrefix = currentClass.getClassName() + "."
				+ currentMethod.getMethodName()+"." + registerAlloc.getLabel()+".";
			//registerAlloc.backRoll();
			output.append(labelPrefix + "LOOP:\n");
			AST2TAC(node.jjtGetChild(0), rootTemps);
			output.append("if "+rootTemps.get(0)+" <> true goto " 
					+ labelPrefix + "BREAKPOINT\n");
			AST2TAC(node.jjtGetChild(1), rootTemps);
			output.append("goto " + labelPrefix + "LOOP\n");
			output.append(labelPrefix + "BREAKPOINT:\n");
			return true;
		}
		else if (node.getName().equals("do{Statement} while( Exp )")){
			String labelPrefix = currentClass.getClassName() + "."
				+ currentMethod.getMethodName()+"." + registerAlloc.getLabel()+".";
			//registerAlloc.backRoll();
			output.append(labelPrefix + "LOOP:\n");
			ArrayList <String> rootTemps = new ArrayList <String>();
			AST2TAC(node.jjtGetChild(0), rootTemps);
			AST2TAC(node.jjtGetChild(1), rootTemps);
			output.append("if "+rootTemps.get(0)+" <> true goto " 
					+ labelPrefix + "LOOP\n");
			return true;
		}
		return false;
	}
	
	private void postStatementProcess(ASTStatement node,
			ArrayList <String> childTemps){
		if (node.getName().equals("System.out.println ( Exp );")){
			for(int i = 0; i < childTemps.size(); i++)
				output.append("param "+childTemps.get(i)+"\n");
			output.append("call System.out.println,"+childTemps.size()+"\n");
		}
		else if (node.getName().equals("block")){
			registerAlloc.leaveBlock();
		}
		else if (node.getName().equals("id = Exp ;")){

			if (tempsTypeList.get(childTemps.get(0)) != null){
				if (tempsTypeList.get(childTemps.get(0)) == "*")
				output.append("*" + childTemps.get(0) +" := "
						+ childTemps.get(1) +"\n");
			}else
				output.append(childTemps.get(0) +" := "
						+ childTemps.get(1) +"\n");
		}
		else if (node.getName().equals("id [ Exp ]= Exp ;")){
			String reg = registerAlloc.get();
	    	output.append(reg + " := "
	    			+ childTemps.get(1)+" * _w\n");
			output.append(childTemps.get(0) +"[ " + reg + " ] := "
					+ childTemps.get(2) +"\n");
		}
		
	}
	
	private void preExpProcess(ASTExp node,
			ArrayList <String> parentTemps,
			ArrayList <String> childTemps){
		//output.append(node.getName()+ node.getType()+"\n");
		if (node.getName().equals("exp")){
			if (node.getType() == null){
					
			}
			else if (node.jjtGetChild(0) instanceof ASTTerminal){
				ASTTerminal nodeTerminal= 
					(ASTTerminal) node.jjtGetChild(0); 
				if (isMemberField(nodeTerminal.getName())){
					/*String resReg = registerAlloc.get();
					childTemps.add(resReg);
					parentTemps.add(resReg);*/
					parentTemps.add(registerAlloc.get());
					registerAlloc.backRoll();
				}
				else
					parentTemps.add(nodeTerminal.getName());
				/*parentTemps.add(registerAlloc.get());
				registerAlloc.backRoll();*/
			}
			else if (node.jjtGetChild(0) instanceof ASTNewInstance){
				parentTemps.add(registerAlloc.get());
				registerAlloc.backRoll();
			}
			else if (node.jjtGetChild(0) instanceof ASTnewIntArray){
				parentTemps.add(registerAlloc.get());
				registerAlloc.backRoll();
			}
			else if (node.jjtGetChild(0) instanceof ASTnewDoubleArray){
				parentTemps.add(registerAlloc.get());
				registerAlloc.backRoll();
			}
			else if (node.isPreOnly() && node.getPrefix().equals("!exp")){
				String resReg = registerAlloc.get();
				childTemps.add(resReg);
				parentTemps.add(resReg);
			}
			
		}
		else if (node.getName().equals("exp op exp")){
			String resReg = registerAlloc.get();
			childTemps.add(resReg);
			parentTemps.add(resReg);
		}else if (node.getName().equals("exp [exp]")){
			String resReg = registerAlloc.get();
			childTemps.add(resReg);
			parentTemps.add(resReg);
		}
		else if (node.getName().equals("exp .length")){
			String resReg = registerAlloc.get();
			childTemps.add(resReg);
			parentTemps.add(resReg);
		}
		else if (node.getName().equals("exp .id(expList)")){
			output.append("// start of function call\n");
			String resReg = registerAlloc.get();
			childTemps.add(resReg);
			parentTemps.add(resReg);
		}
		
		if (node.getPrefix().equals("(exp)") && node.getName().equals("exp")){
			String resReg = registerAlloc.get();
			//childTemps.add(resReg);
			parentTemps.add(resReg);
			registerAlloc.backRoll();
		}
		//System.err.println(node.getPrefix());
	}
	
	private void postExpProcess(ASTExp node,
			ArrayList <String> childTemps){
		if (node.getName().equals("exp")){
			if (node.getType() == null){
				
			}
			else if (node.jjtGetChild(0) instanceof ASTTerminal){
				/*ASTTerminal nodeTerminal= 
					(ASTTerminal) node.jjtGetChild(0); 
				if (isMemberField(nodeTerminal.getName())){
					output.append(childTemps.get(0)+ " := _thiscall["
							+ nodeTerminal.getName() +"]\n");
				}*/
			}
			else if (node.isPreOnly() && node.getPrefix().equals("!exp")){
				if (isMemberField(childTemps.get(1)))
					childTemps.set(1, "this." + childTemps.get(1));
				output.append(childTemps.get(0)+" := ! "+ childTemps.get(1)+ "\n");
			}
			
		}
		
		else if (node.getName().equals("exp op exp")){
			{
				output.append(childTemps.get(0)+" := "
						+ childTemps.get(1)+" "
						+ childTemps.get(2)+" "
						+ childTemps.get(3)+ "\n");
			}/*catch(Exception e){
				
			}*/
		}
	    else if (node.getName().equals("exp [exp]")){
	    	String reg = registerAlloc.get();
	    	output.append(reg + " := "
	    			+ childTemps.get(2)+" * _w\n");
	    	output.append(childTemps.get(0)+" := "
	    			+ childTemps.get(1)+"[ "
					+ reg +" ] \n");
		}
		else if (node.getName().equals("exp .length")){
	    	output.append(childTemps.get(0)+" := "
	    			+ childTemps.get(1)+" - _w\n");
	    	output.append(childTemps.get(0)+" := *"
	    			+ childTemps.get(0)+"\t//load length stored in heap\n");
	    	
		}
		else if (node.getName().equals("exp .id(expList)")){
			String calleeClassName ="??";
			String instanceName = childTemps.get(1);
			if (instanceName.equals("this")){
				calleeClassName = currentClass.getClassName();
				childTemps.set(1, "_thiscall");
			}else if(instanceName.startsWith("_T")){
				calleeClassName = tempsTypeList.get(instanceName);
			}else if(currentMethod != null &&
					currentMethod.getVarByName(instanceName) != null){
				calleeClassName = currentMethod.
					getVarByName(instanceName).getType();
			}else{
				if (currentMethod != null)
					for (MethodParameter method :currentMethod.getParameters())
						if (method.getName().equals(instanceName))
							calleeClassName = method.getType();
			}
			if (currentClass != null && calleeClassName =="??" 
				&& currentClass.getVarByName(instanceName) != null){
				calleeClassName = currentClass.
					getVarByName(instanceName).getType();
			}
			
			output.append("param " + childTemps.get(1)+ "\n");
			output.append("call " + calleeClassName +"."+
					childTemps.get(2) + " , " + childTemps.get(3) +
					"\nreturn " + childTemps.get(0) + "\n");
			output.append("// end of function call\n");
			
		}
		/*if (node.getPrefix().equals("(exp)") && node.getName().equals("exp")){
			output.append(childTemps.get(0)+" := "
					+ childTemps.get(1)+ "\n");
		}*/
	}
	
	private void declInitialVaribles(){
		output.append("_heap_base := $heap_base\t//$heap_top injected by linker\n");
		//output.append("_stack_base := $stack_base\t//$stack_base injected by linker\n");
		output.append("_heap_ptr := _heap_base\n");
		//output.append("_stack_ptr := _stack_base\n");
		output.append("_w := _$word_bytes\n");
		
	}
	private void printTACCodes(){
		System.out.println(output);
		
	}
	
	private int getClassFields(String className){
		ClassDesc classDesc = symbolTable.getClassByName(className);
		int count = classDesc.getMemberVars();
		while(classDesc.getParent() != null){
			classDesc = symbolTable.getClassByName( classDesc.getParent());
			count += classDesc.getMemberVars();
		}
		return count;
	}
	
	private boolean isMemberField(String fieldName){
		if (currentMethod == null || currentClass == null)
			return false;
		else if(currentMethod.getVarByName(fieldName) != null){
			return false;
		}else{
			for (MethodParameter method :currentMethod.getParameters())
				if (method.getName().equals(fieldName))
					return false;
			if (currentClass.getVarByName(fieldName)!= null)
				return true;
		}
		return false;
	}
	private class registerAllocater{
		private ArrayList <Integer> EntryPointRegisterUsage;
		private int current;
		private int currentLabel;
		private ArrayList <Integer> StackOffset;
		private int stackUsage;
		public registerAllocater(){
			EntryPointRegisterUsage = new ArrayList<Integer>();
			current = 0;
			currentLabel = 0;
			StackOffset = new ArrayList<Integer>();
			stackUsage = 0;
		}
		public String get(){
			current++;
			return "_T" + (current -1);
		}
		public String getLabel(){
			int lab = currentLabel;
			currentLabel ++;
			return "DIV" + currentLabel;
		}
		public void backRoll(){
			current--;
		}
		public String getStackSpace(){
			stackUsage++;
			return "_stack_ptr := _stack_ptr + 1 \n";
		}
		public void entryBlock(){
			EntryPointRegisterUsage.add(0,current);
			StackOffset.add(0, stackUsage);
		}
		public int leaveBlock(){	
			//current = EntryPointRegisterUsage.get(0);
			//EntryPointRegisterUsage.remove(0);
			int usage = stackUsage;
			//stackUsage = StackOffset.get(0);
			//StackOffset.remove(0);
			return usage;
			
		}
		
		
	}
}
